/*
 * Copyright 2011 Christopher Barakian
 *
 * This library is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Lesser General Public
 * License as published by the Free Software Foundation; either
 * version 2.1 of the License, or (at your option) any later version.
 *
 * This library is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 * Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public
 * License along with this library; if not, write to the Free Software
 * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA
 */


/*Directx 9 3D test*/
#include "resources.h"
#include <windows.h>
#include <d3d9.h>
#include <d3dx9.h>
#include <d3dx9math.h>
#include <stdio.h>
#include "globals.h"
#include "d3dx9test.h"

#define D3DFVF_CUSTOMVERTEX (D3DFVF_XYZ | D3DFVF_TEX1)
#define TEST_TIMEOUT 5000.0
#define XROT_VAL 10.0f
#define YROT_VAL 11.0f
#define ZROT_VAL 12.0f
#define ROT_ANG_FREQ 0.02

WINE_DEFAULT_DEBUG_CHANNEL(dxdiag);

typedef struct {
    float x, y, z, tu, tv;
} Vertex;

/*Checks if a test instance is running*/
volatile int gd3d_running = FALSE;

/*Time variables*/
double gd3d_dt;
double gd3d_t_cur;
double gd3d_t_last;
double gd3d_t_elapsed;

/*Handlers */
HWND gd3d_hWnd;
LPDIRECT3D9 gd3d_pD3D9;
LPDIRECT3DDEVICE9 gd3d_pDevice;
LPDIRECT3DVERTEXBUFFER9 gd3d_pVertexBuffer;
LPDIRECT3DTEXTURE9 gd3d_pTexture;
LPDIRECT3DSURFACE9 gd3d_pSurface;
D3DLOCKED_RECT gd3d_lRect;

/*Cube veritces for each side of the cube*/
Vertex D3DTest_cubeVertices[] = {
    {-1.0f, 1.0f, -1.0f, 0.0f, 0.0f},
    {1.0f, 1.0f, -1.0f, 1.0f, 0.0f},
    {-1.0f, -1.0f, -1.0f, 0.0f, 1.0f},
    {1.0f, -1.0f, -1.0f, 1.0f, 1.0f},

    {-1.0f, 1.0f, 1.0f, 1.0f, 0.0f},
    {-1.0f, -1.0f, 1.0f, 1.0f, 1.0f},
    {1.0f, 1.0f, 1.0f, 0.0f, 0.0f},
    {1.0f, -1.0f, 1.0f, 0.0f, 1.0f},

    {-1.0f, 1.0f, 1.0f, 0.0f, 0.0f},
    {1.0f, 1.0f, 1.0f, 1.0f, 0.0f},
    {-1.0f, 1.0f, -1.0f, 0.0f, 1.0f},
    {1.0f, 1.0f, -1.0f, 1.0f, 1.0f},

    {-1.0f, -1.0f, 1.0f, 0.0f, 0.0f},
    {-1.0f, -1.0f, -1.0f, 1.0f, 0.0f},
    {1.0f, -1.0f, 1.0f, 0.0f, 1.0f},
    {1.0f, -1.0f, -1.0f, 1.0f, 1.0f},

    {1.0f, 1.0f, -1.0f, 0.0f, 0.0f},
    {1.0f, 1.0f, 1.0f, 1.0f, 0.0f},
    {1.0f, -1.0f, -1.0f, 0.0f, 1.0f},
    {1.0f, -1.0f, 1.0f, 1.0f, 1.0f},

    {-1.0f, 1.0f, -1.0f, 1.0f, 0.0f},
    {-1.0f, -1.0f, -1.0f, 1.0f, 1.0f},
    {-1.0f, 1.0f, 1.0f, 0.0f, 0.0f},
    {-1.0f, -1.0f, 1.0f, 0.0f, 1.0f}
};

void D3DTest_initD3D(void);
void D3DTest_render(void);
void D3DTest_shutdown(void);
void D3DTest_loadTexture(void);

LRESULT CALLBACK D3DTest_DlgProc(HWND hwnd, UINT msg, WPARAM wPARAM,
                                 LPARAM lParam);

void D3DTest_initD3D(void)
{
    /*temporary variables */
    D3DDISPLAYMODE D3Ddm;
    D3DPRESENT_PARAMETERS D3Dpp;
    D3DMATRIX mat_proj;
    void *pVertices = NULL;

    /* Create a Direct3D object */
    gd3d_pD3D9 = Direct3DCreate9(D3D_SDK_VERSION);

    if (!gd3d_pD3D9) {
        printf("Direct3DCreate9() - Failed\n");
        abort();
    }

    IDirect3D9_GetAdapterDisplayMode(gd3d_pD3D9, D3DADAPTER_DEFAULT,
                                     &D3Ddm);

    /* Fill presentation parameters */
    ZeroMemory(&D3Dpp, sizeof(D3Dpp));

    D3Dpp.Windowed = TRUE;
    D3Dpp.SwapEffect = D3DSWAPEFFECT_DISCARD;
    D3Dpp.BackBufferFormat = D3Ddm.Format;
    D3Dpp.EnableAutoDepthStencil = TRUE;
    D3Dpp.AutoDepthStencilFormat = D3DFMT_D16;
    D3Dpp.PresentationInterval = D3DPRESENT_INTERVAL_IMMEDIATE;

    /* Create the Direct3D device */
    gd3d_pDevice = NULL;
    if (FAILED
        (IDirect3D9_CreateDevice
         (gd3d_pD3D9, D3DADAPTER_DEFAULT, D3DDEVTYPE_HAL, gd3d_hWnd,
          D3DCREATE_SOFTWARE_VERTEXPROCESSING, &D3Dpp, &gd3d_pDevice))) {
        printf("CreateDevice() - Failed\n");
        abort();
    }

    /* Load the Texture */
    D3DTest_loadTexture();

    /* Create vertex buffer */
    gd3d_pVertexBuffer = NULL;
    if (FAILED
        (IDirect3DDevice9_CreateVertexBuffer
         (gd3d_pDevice, 24 * sizeof(Vertex), 0, D3DFVF_CUSTOMVERTEX,
          D3DPOOL_DEFAULT, &gd3d_pVertexBuffer, NULL))) {
        printf("CreateVertexBuffer() - Failed\n");
        abort();
    }

    /* Locks a range of vertex data and loads a pointer to vertex buffer memory */
    IDirect3DVertexBuffer9_Lock(gd3d_pVertexBuffer, 0,
                                sizeof(D3DTest_cubeVertices),
                                (void **) &pVertices, 0);
    memcpy(pVertices, D3DTest_cubeVertices, sizeof(D3DTest_cubeVertices));
    IDirect3DVertexBuffer9_Unlock(gd3d_pVertexBuffer);

    /* Disable lighting, enable z-buffer, and set perspective projection */
    IDirect3DDevice9_SetRenderState(gd3d_pDevice, D3DRS_LIGHTING, FALSE);
    IDirect3DDevice9_SetRenderState(gd3d_pDevice, D3DRS_ZENABLE, TRUE);

    D3DXMatrixPerspectiveFovLH(&mat_proj, D3DXToRadian(45.0f),
                               640.0f / 480.0f, 0.1f, 100.0f);
    IDirect3DDevice9_SetTransform(gd3d_pDevice, D3DTS_PROJECTION,
                                  &mat_proj);
}

/* Load in the texture */
void D3DTest_loadTexture(void)
{
    /* Image Parameters */
    DWORD width = 128;
    DWORD height = 128;
    DWORD nPixels = width * height;
    int bpp = 3;
    int offset = sizeof(BITMAPINFO) + 1;

    UINT i;
    BYTE *buffer;
    DWORD *pData;
    HRSRC hResource;
    HGLOBAL hResData;

    /* Get bitmap resource */
    hResource = FindResourceA(g_hinstance, MAKEINTRESOURCE(IDB_WINE),
                              RT_BITMAP);
    if (!hResource) {
        printf("FindResourceA() failed\n");
        abort();
    }

    hResData = LoadResource(g_hinstance, hResource);
    if (!hResData) {
        printf("LoadResource() failed\n");
        abort();
    }

    buffer = (BYTE *) LockResource(hResData);
    /* Create the texture */
    if (FAILED
        (IDirect3DDevice9_CreateTexture
         (gd3d_pDevice, width, height, 8, 0, D3DFMT_X8R8G8B8,
          D3DPOOL_MANAGED, &gd3d_pTexture, 0))) {
        printf("CreateTexture() failed\n");
    }
    IDirect3DTexture9_GetSurfaceLevel(gd3d_pTexture, 0, &gd3d_pSurface);

    /* Lock texture for copying image */
    IDirect3DTexture9_LockRect(gd3d_pTexture, 0, &gd3d_lRect, 0,
                               D3DLOCK_DISCARD);

    /* Get a pointer into texture buffer data */
    pData = gd3d_lRect.pBits;

    /* Copy over bitmap data into texture buffer */
    for (i = 0; i < nPixels; i++) {
        pData[i] =
            D3DCOLOR_XRGB(buffer[offset + bpp * i],
                          buffer[offset + bpp * i + 2],
                          buffer[offset + bpp * i + 1]);
    }

    IDirect3DTexture9_UnlockRect(gd3d_pTexture, 0);

    /* Set hyperbolic interpolation filters for viewing texture */
    IDirect3DDevice9_SetSamplerState(gd3d_pDevice, 0, D3DSAMP_MINFILTER,
                                     D3DTEXF_LINEAR);
    IDirect3DDevice9_SetSamplerState(gd3d_pDevice, 0, D3DSAMP_MAGFILTER,
                                     D3DTEXF_LINEAR);

    FreeResource(hResData);

}

/* Render the Scene */

void D3DTest_render(void)
{
    D3DXMATRIX mat_world;
    D3DXMATRIX mat_trans;
    D3DXMATRIX mat_rot;

    static float xrot = 0.0f;
    static float yrot = 0.0f;
    static float zrot = 0.0f;

    static float du;

    du = gd3d_dt * ROT_ANG_FREQ;

    /* Clear the buffer and z-buffer for rendering */
    IDirect3DDevice9_Clear(gd3d_pDevice, 0, NULL, D3DCLEAR_TARGET
                           | D3DCLEAR_ZBUFFER, D3DCOLOR_COLORVALUE(0.0f,
                                                                   0.0f,
                                                                   0.0f,
                                                                   1.0f),
                           1.0f, 0);

    /* Update cube rotation parameters */
    xrot += XROT_VAL * du;
    yrot += YROT_VAL * du;
    zrot += ZROT_VAL * du;

    D3DXMatrixTranslation(&mat_trans, 0.0f, 0.0f, 5.0f);
    D3DXMatrixRotationYawPitchRoll(&mat_rot, D3DXToRadian(xrot),
                                   D3DXToRadian(yrot), D3DXToRadian(zrot));

    /* Get matrix transform from object to world coordinates */
    mat_world = *D3DXMatrixMultiply(&mat_world, &mat_rot, &mat_trans);
    IDirect3DDevice9_SetTransform(gd3d_pDevice, D3DTS_WORLD, &mat_world);

    /* Initialize scene, textures */
    IDirect3DDevice9_BeginScene(gd3d_pDevice);
    IDirect3DDevice9_SetTexture(gd3d_pDevice, 0,
                                (IDirect3DBaseTexture9 *) gd3d_pTexture);
    IDirect3DDevice9_SetStreamSource(gd3d_pDevice, 0, gd3d_pVertexBuffer,
                                     0, sizeof(Vertex));
    IDirect3DDevice9_SetFVF(gd3d_pDevice, D3DFVF_CUSTOMVERTEX);

    /* Render polygons */
    IDirect3DDevice9_DrawPrimitive(gd3d_pDevice, D3DPT_TRIANGLESTRIP, 0,
                                   2);
    IDirect3DDevice9_DrawPrimitive(gd3d_pDevice, D3DPT_TRIANGLESTRIP, 4,
                                   2);
    IDirect3DDevice9_DrawPrimitive(gd3d_pDevice, D3DPT_TRIANGLESTRIP, 8,
                                   2);
    IDirect3DDevice9_DrawPrimitive(gd3d_pDevice, D3DPT_TRIANGLESTRIP, 12,
                                   2);
    IDirect3DDevice9_DrawPrimitive(gd3d_pDevice, D3DPT_TRIANGLESTRIP, 16,
                                   2);
    IDirect3DDevice9_DrawPrimitive(gd3d_pDevice, D3DPT_TRIANGLESTRIP, 20,
                                   2);

    /* Finish scene and render to screen */
    IDirect3DDevice9_EndScene(gd3d_pDevice);
    IDirect3DDevice9_Present(gd3d_pDevice, NULL, NULL, NULL, NULL);

}

void D3DTest_shutdown(void)
{
    if (gd3d_pTexture) {
        IDirect3DTexture9_Release(gd3d_pTexture);
    }
    if (gd3d_pVertexBuffer) {
        IDirect3DTexture9_Release(gd3d_pVertexBuffer);
    }
    if (gd3d_pDevice) {
        IDirect3DTexture9_Release(gd3d_pDevice);
    }
    if (gd3d_pD3D9) {
        IDirect3DTexture9_Release(gd3d_pD3D9);
    }
}

void D3DTest_testD3D(void)
{

    int retval;
    WNDCLASSEXA wc = { 0 };
    MSG uMsg;

    /* check whether test is running before continuing */
    if (gd3d_running) {
        return;
    }

    gd3d_running = TRUE;

    retval
        = MessageBoxA(NULL,
                      "This will test DirectX 3D 9 on this device. Click Yes to proceed?",
                      "Direct 3D Test", MB_YESNO);

    if (retval != IDYES) {
        gd3d_running = FALSE;
        return;
    }

    memset(&uMsg, 0, sizeof(uMsg));
    wc.lpszClassName = "dxdiag";
    wc.cbSize = sizeof(WNDCLASSEXA);
    wc.style = CS_HREDRAW | CS_VREDRAW;
    wc.lpfnWndProc = D3DTest_DlgProc;

    RegisterClassExA(&wc);

    gd3d_hWnd = CreateWindowExA(0, "dxdiag", "Direct 3D Test",
                                WS_OVERLAPPEDWINDOW | WS_VISIBLE, 0, 0,
                                800, 600, 0, 0, 0, 0);

    ShowWindow(gd3d_hWnd, SW_SHOWNORMAL);
    UpdateWindow(gd3d_hWnd);

    D3DTest_initD3D();

    gd3d_t_elapsed = 0;
    gd3d_t_last = timeGetTime();

    while (uMsg.message != WM_QUIT) {
        if (gd3d_t_elapsed >= TEST_TIMEOUT) {
            PostQuitMessage(0);
            DestroyWindow(gd3d_hWnd);
        }

        if (PeekMessageW(&uMsg, NULL, 0, 0, PM_REMOVE)) {
            TranslateMessage(&uMsg);
            DispatchMessageW(&uMsg);
        }

        else {
            gd3d_t_cur = timeGetTime();
            gd3d_dt = gd3d_t_cur - gd3d_t_last;
            gd3d_t_last = gd3d_t_cur;
            gd3d_t_elapsed += gd3d_dt;
            D3DTest_render();
        }
    }

    D3DTest_shutdown();
    MessageBoxA(NULL, "Did you see the spinning box on the screen?",
                "Direct 3D Test", MB_YESNO);
    UnregisterClassA("dxdiag", wc.hInstance);
    gd3d_running = FALSE;
}

LRESULT CALLBACK D3DTest_DlgProc(HWND hWnd, UINT uMsg, WPARAM wParam,
                                 LPARAM lParam)
{
    switch (uMsg) {
    case WM_KEYDOWN:
        {
            switch (wParam) {
            case VK_ESCAPE:
                PostQuitMessage(0);
                DestroyWindow(hWnd);
                break;
            }
        }

    case WM_CLOSE:
        PostQuitMessage(0);
        DestroyWindow(hWnd);
        break;
    case WM_DESTROY:
        PostQuitMessage(0);
        DestroyWindow(hWnd);
        break;

    default:
        return DefWindowProcA(hWnd, uMsg, wParam, lParam);
        break;
    }

    return 0;
}
